#ifndef __OCPP_Cesium_Cartesian2_H__
#define __OCPP_Cesium_Cartesian2_H__

#include "CesiumMath.h"

namespace OCPP
{
	namespace Cesium
	{
		class _CesiumExport Cartesian2
		{
		public:
			double x, y;
			Cartesian2(double _x = 0.0, double _y = 0.0)
				: x(_x)
				, y(_y)
			{

			}

			Cartesian2(const Cartesian2& other)
			{
				this->x = other.x;
				this->y = other.y;
			}

			Cartesian2& operator = (const Cartesian2& fScaler)
			{
				x = fScaler.x;
				y = fScaler.y;

				return (Cartesian2&)*this;
			}

			/**
			 * Creates a Cartesian2 instance from x, y and z coordinates.
			 *
			 * @param {number} x The x coordinate.
			 * @param {number} y The y coordinate.
			 * @param {number} z The z coordinate.
			 * @param {Cartesian2} [result] The object onto which to store the result.
			 * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
			 */
			static Cartesian2 fromElements(double x, double y) {
				Cartesian2 result;
				result.x = x;
				result.y = y;
				return result;
			};

			/**
			 * Creates a Cartesian2 instance from an existing Cartesian4.  This simply takes the
			 * x, y, and z properties of the Cartesian4 and drops w.
			 * @function
			 *
			 * @param {Cartesian4} cartesian The Cartesian4 instance to create a Cartesian2 instance from.
			 * @param {Cartesian2} [result] The object onto which to store the result.
			 * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
			 */
			Cartesian2 fromCartesian3(Cartesian3 cartesian4);
			Cartesian2 fromCartesian4(Cartesian4 cartesian4);

			/**
			 * The number of elements used to pack the object into an array1.
			 * @type {number}
			 */
			inline static const int packedLength = 3;

			/**
			 * Stores the provided instance into the provided array1.
			 *
			 * @param {Cartesian2} value The value to pack.
			 * @param {number[]} array1 The array1 to pack into.
			 * @param {number} [startingIndex=0] The index into the array1 at which to start packing the elements.
			 *
			 * @returns {number[]} The array1 that was packed into
			 */
			static DoubleVector pack(Cartesian2& value, DoubleVector array1, size_t startingIndex = 0) {
				array1[startingIndex++] = value.x;
				array1[startingIndex++] = value.y;

				return array1;
			};

			/**
			 * Retrieves an instance from a packed array1.
			 *
			 * @param {number[]} array1 The packed array1.
			 * @param {number} [startingIndex=0] The starting index of the element to be unpacked.
			 * @param {Cartesian2} [result] The object into which to store the result.
			 * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
			 */
			static Cartesian2 unpack(DoubleVector array1, size_t startingIndex = 0) {
				Cartesian2 result;
				result.x = array1[startingIndex++];
				result.y = array1[startingIndex++];
				return result;
			};

			/**
			 * Flattens an array1 of Cartesian2s into an array1 of components.
			 *
			 * @param {Cartesian2[]} array1 The array1 of cartesians to pack.
			 * @param {number[]} [result] The array1 onto which to store the result. If this is a typed array1, it must have array1.length * 3 components, else a {@link DeveloperError} will be thrown. If it is a regular array1, it will be resized to have (array1.length * 3) elements.
			 * @returns {number[]} The packed array1.
			 */
			static DoubleVector packArray(std::vector<Cartesian2> array1) {
				size_t length = array1.size();
				size_t resultLength = length * 2;
				DoubleVector result;
				result.resize(resultLength);

				for (size_t i = 0; i < length; ++i) {
					Cartesian2::pack(array1[i], result, i * 2);
				}
				return result;
			};

			/**
			 * Unpacks an array1 of cartesian components into an array1 of Cartesian2s.
			 *
			 * @param {number[]} array1 The array1 of components to unpack.
			 * @param {Cartesian2[]} [result] The array1 onto which to store the result.
			 * @returns {Cartesian2[]} The unpacked array1.
			 */
			static std::vector<Cartesian2> unpackArray(DoubleVector array1) {
				size_t length = array1.size();
				std::vector<Cartesian2> result;
				result.resize(length / 2);
				for (size_t i = 0; i < length; i += 2) {
					size_t index = i / 2;
					result[index] = (Cartesian2::unpack(array1, i));
				}
				return result;
			};

			/**
			 * Creates a Cartesian2 from three consecutive elements in an array1.
			 * @function
			 *
			 * @param {number[]} array1 The array1 whose three consecutive elements correspond to the x, y, and z components, respectively.
			 * @param {number} [startingIndex=0] The offset into the array1 of the first element, which corresponds to the x component.
			 * @param {Cartesian2} [result] The object onto which to store the result.
			 * @returns {Cartesian2} The modified result parameter or a new Cartesian2 instance if one was not provided.
			 *
			 * @example
			 * // Create a Cartesian2 with (1.0, 2.0, 3.0)
			 * const v = [1.0, 2.0, 3.0];
			 * const p = Cesium.Cartesian2.fromArray(v);
			 *
			 * // Create a Cartesian2 with (1.0, 2.0, 3.0) using an offset into an array1
			 * const v2 = [0.0, 0.0, 1.0, 2.0, 3.0];
			 * const p2 = Cesium.Cartesian2.fromArray(v2, 2);
			 */
			static Cartesian2 fromArray(DoubleVector array1, size_t startingIndex = 0)
			{
				return Cartesian2::unpack(array1, startingIndex);
			}

			/**
			 * Computes the value of the maximum component for the supplied Cartesian.
			 *
			 * @param {Cartesian2} cartesian The cartesian to use.
			 * @returns {number} The value of the maximum component.
			 */
			static double maximumComponent(Cartesian2& cartesian) {
				return (std::max)(cartesian.x, cartesian.y);
			};

			/**
			 * Computes the value of the minimum component for the supplied Cartesian.
			 *
			 * @param {Cartesian2} cartesian The cartesian to use.
			 * @returns {number} The value of the minimum component.
			 */
			static double minimumComponent(Cartesian2& cartesian) {
				return (std::min)(cartesian.x, cartesian.y);
			};

			/**
			 * Compares two Cartesians and computes a Cartesian which contains the minimum components of the supplied Cartesians.
			 *
			 * @param {Cartesian2} first A cartesian to compare.
			 * @param {Cartesian2} second A cartesian to compare.
			 * @param {Cartesian2} result The object into which to store the result.
			 * @returns {Cartesian2} A cartesian with the minimum components.
			 */
			static Cartesian2 minimumByComponent(Cartesian2& first, Cartesian2& second) {
				Cartesian2 result;
				result.x = (std::min)(first.x, second.x);
				result.y = (std::min)(first.y, second.y);

				return result;
			};

			/**
			 * Compares two Cartesians and computes a Cartesian which contains the maximum components of the supplied Cartesians.
			 *
			 * @param {Cartesian2} first A cartesian to compare.
			 * @param {Cartesian2} second A cartesian to compare.
			 * @param {Cartesian2} result The object into which to store the result.
			 * @returns {Cartesian2} A cartesian with the maximum components.
			 */
			static Cartesian2 maximumByComponent(Cartesian2& first, Cartesian2& second) {
				Cartesian2 result;
				result.x = (std::max)(first.x, second.x);
				result.y = (std::max)(first.y, second.y);
				return result;
			};

			/**
			 * Constrain a value to lie between two values.
			 *
			 * @param {Cartesian2} cartesian The value to clamp.
			 * @param {Cartesian2} min The minimum bound.
			 * @param {Cartesian2} max The maximum bound.
			 * @param {Cartesian2} result The object into which to store the result.
			 * @returns {Cartesian2} The clamped value such that min <= value <= max.
			 */
			static Cartesian2 clamp(Cartesian2& value, Cartesian2& min, Cartesian2& max) {
				double x = CesiumMath::clamp(value.x, min.x, max.x);
				double y = CesiumMath::clamp(value.y, min.y, max.y);

				Cartesian2 result;
				result.x = x;
				result.y = y;

				return result;
			};

			/**
			 * Computes the provided Cartesian's squared magnitude.
			 *
			 * @param {Cartesian2} cartesian The Cartesian instance whose squared magnitude is to be computed.
			 * @returns {number} The squared magnitude.
			 */
			static double magnitudeSquared(Cartesian2& cartesian) {
				return (
					cartesian.x * cartesian.x +
					cartesian.y * cartesian.y
					);
			};

			/**
			 * Computes the Cartesian's magnitude (length).
			 *
			 * @param {Cartesian2} cartesian The Cartesian instance whose magnitude is to be computed.
			 * @returns {number} The magnitude.
			 */
			static double magnitude(Cartesian2 cartesian) {
				return sqrt(Cartesian2::magnitudeSquared(cartesian));
			};

			/**
			 * Computes the distance between two points.
			 *
			 * @param {Cartesian2} left The first point to compute the distance from.
			 * @param {Cartesian2} right The second point to compute the distance to.
			 * @returns {number} The distance between two points.
			 *
			 * @example
			 * // Returns 1.0
			 * const d = Cesium.Cartesian2.distance(new Cesium.Cartesian2(1.0, 0.0, 0.0), new Cesium.Cartesian2(2.0, 0.0, 0.0));
			 */
			static double distance(Cartesian2& left, Cartesian2& right) {
				Cartesian2 distanceScratch = Cartesian2::subtract(left, right);
				return Cartesian2::magnitude(distanceScratch);
			};

			/**
			 * Computes the squared distance between two points.  Comparing squared distances
			 * using this function is more efficient than comparing distances using {@link Cartesian2#distance}.
			 *
			 * @param {Cartesian2} left The first point to compute the distance from.
			 * @param {Cartesian2} right The second point to compute the distance to.
			 * @returns {number} The distance between two points.
			 *
			 * @example
			 * // Returns 4.0, not 2.0
			 * const d = Cesium.Cartesian2.distanceSquared(new Cesium.Cartesian2(1.0, 0.0, 0.0), new Cesium.Cartesian2(3.0, 0.0, 0.0));
			 */
			static double distanceSquared(Cartesian2& left, Cartesian2& right) {
				auto distanceScratch = Cartesian2::subtract(left, right);
				return Cartesian2::magnitudeSquared(distanceScratch);
			};

			/**
			 * Computes the normalized form of the supplied Cartesian.
			 *
			 * @param {Cartesian2} cartesian The Cartesian to be normalized.
			 * @param {Cartesian2} result The object onto which to store the result.
			 * @returns {Cartesian2} The modified result parameter.
			 */
			static Cartesian2 normalize(Cartesian2& cartesian) {
				double magnitude = Cartesian2::magnitude(cartesian);
				Cartesian2 result;
				result.x = cartesian.x / magnitude;
				result.y = cartesian.y / magnitude;

				//>>includeStart('debug', pragmas.debug);
				//if (isNaN(result.x) || isNaN(result.y) || isNaN(result.z)) {
				//	throw new DeveloperError("normalized result is not a number");
				//}
				//>>includeEnd('debug');

				return result;
			};

			/**
			 * Computes the dot (scalar) product of two Cartesians.
			 *
			 * @param {Cartesian2} left The first Cartesian.
			 * @param {Cartesian2} right The second Cartesian.
			 * @returns {number} The dot product.
			 */
			static double dot(Cartesian2& left, Cartesian2& right) {
				return left.x * right.x + left.y * right.y;
			};

			/**
			 * Computes the componentwise product of two Cartesians.
			 *
			 * @param {Cartesian2} left The first Cartesian.
			 * @param {Cartesian2} right The second Cartesian.
			 * @param {Cartesian2} result The object onto which to store the result.
			 * @returns {Cartesian2} The modified result parameter.
			 */
			static Cartesian2 multiplyComponents(Cartesian2& left, Cartesian2& right) {
				Cartesian2 result;
				result.x = left.x * right.x;
				result.y = left.y * right.y;
				return result;
			};

			/**
			 * Computes the componentwise quotient of two Cartesians.
			 *
			 * @param {Cartesian2} left The first Cartesian.
			 * @param {Cartesian2} right The second Cartesian.
			 * @param {Cartesian2} result The object onto which to store the result.
			 * @returns {Cartesian2} The modified result parameter.
			 */
			static Cartesian2 divideComponents(Cartesian2& left, Cartesian2& right) {
				Cartesian2 result;

				result.x = left.x / right.x;
				result.y = left.y / right.y;
				return result;
			};

			/**
			 * Computes the componentwise sum of two Cartesians.
			 *
			 * @param {Cartesian2} left The first Cartesian.
			 * @param {Cartesian2} right The second Cartesian.
			 * @param {Cartesian2} result The object onto which to store the result.
			 * @returns {Cartesian2} The modified result parameter.
			 */
			static Cartesian2 add(Cartesian2& left, Cartesian2& right) {
				Cartesian2 result;

				result.x = left.x + right.x;
				result.y = left.y + right.y;
				return result;
			};

			/**
			 * Computes the componentwise difference of two Cartesians.
			 *
			 * @param {Cartesian2} left The first Cartesian.
			 * @param {Cartesian2} right The second Cartesian.
			 * @param {Cartesian2} result The object onto which to store the result.
			 * @returns {Cartesian2} The modified result parameter.
			 */
			static Cartesian2 subtract(Cartesian2& left, Cartesian2& right) {
				Cartesian2 result;
				result.x = left.x - right.x;
				result.y = left.y - right.y;
				return result;
			};

			/**
			 * Multiplies the provided Cartesian componentwise by the provided scalar.
			 *
			 * @param {Cartesian2} cartesian The Cartesian to be scaled.
			 * @param {number} scalar The scalar to multiply with.
			 * @param {Cartesian2} result The object onto which to store the result.
			 * @returns {Cartesian2} The modified result parameter.
			 */
			static Cartesian2 multiplyByScalar(Cartesian2& cartesian, double scalar) {
				Cartesian2 result;
				result.x = cartesian.x * scalar;
				result.y = cartesian.y * scalar;
				return result;
			};

			/**
			 * Divides the provided Cartesian componentwise by the provided scalar.
			 *
			 * @param {Cartesian2} cartesian The Cartesian to be divided.
			 * @param {number} scalar The scalar to divide by.
			 * @param {Cartesian2} result The object onto which to store the result.
			 * @returns {Cartesian2} The modified result parameter.
			 */
			static Cartesian2 divideByScalar(Cartesian2& cartesian, double scalar) {
				Cartesian2 result;
				result.x = cartesian.x / scalar;
				result.y = cartesian.y / scalar;
				return result;
			};

			/**
			 * Negates the provided Cartesian.
			 *
			 * @param {Cartesian2} cartesian The Cartesian to be negated.
			 * @param {Cartesian2} result The object onto which to store the result.
			 * @returns {Cartesian2} The modified result parameter.
			 */
			static Cartesian2 negate(Cartesian2& cartesian) {
				Cartesian2 result;
				result.x = -cartesian.x;
				result.y = -cartesian.y;
				return result;
			};

			/**
			 * Computes the absolute value of the provided Cartesian.
			 *
			 * @param {Cartesian2} cartesian The Cartesian whose absolute value is to be computed.
			 * @param {Cartesian2} result The object onto which to store the result.
			 * @returns {Cartesian2} The modified result parameter.
			 */
			static Cartesian2 abs(Cartesian2& cartesian) {
				Cartesian2 result;
				result.x = std::abs(cartesian.x);
				result.y = std::abs(cartesian.y);
				return result;
			};

			/**
			 * Computes the linear interpolation or extrapolation at t using the provided cartesians.
			 *
			 * @param {Cartesian2} start The value corresponding to t at 0.0.
			 * @param {Cartesian2} end The value corresponding to t at 1.0.
			 * @param {number} t The point along t at which to interpolate.
			 * @param {Cartesian2} result The object onto which to store the result.
			 * @returns {Cartesian2} The modified result parameter.
			 */
			static Cartesian2 lerp(Cartesian2& start, Cartesian2& end, double t) {
				Cartesian2 lerpScratch = Cartesian2::multiplyByScalar(end, t);
				Cartesian2 result = Cartesian2::multiplyByScalar(start, 1.0 - t);
				return Cartesian2::add(lerpScratch, result);
			};

			/**
			 * Returns the angle, in radians, between the provided Cartesians.
			 *
			 * @param {Cartesian2} left The first Cartesian.
			 * @param {Cartesian2} right The second Cartesian.
			 * @returns {number} The angle between the Cartesians.
			 */
			static double angleBetween(Cartesian2& left, Cartesian2& right) {
				auto angleBetweenScratch = Cartesian2::normalize(left);
				auto angleBetweenScratch2 = Cartesian2::normalize(right);
				auto cosine = Cartesian2::dot(angleBetweenScratch, angleBetweenScratch2);
				return CesiumMath::acosClamped(cosine);
			};

			/**
			 * Returns the axis that is most orthogonal to the provided Cartesian.
			 *
			 * @param {Cartesian2} cartesian The Cartesian on which to find the most orthogonal axis.
			 * @param {Cartesian2} result The object onto which to store the result.
			 * @returns {Cartesian2} The most orthogonal axis.
			 */
			static Cartesian2 mostOrthogonalAxis(Cartesian2& cartesian) {
				auto f = Cartesian2::normalize(cartesian);
				f = Cartesian2::abs(f);

				Cartesian2 result;
				if (f.x <= f.y) {
					result = Cartesian2::UNIT_X;
				}
				else {
					result = Cartesian2::UNIT_Y;
				}

				return result;
			};

			/**
			 * Projects vector a onto vector b
			 * @param {Cartesian2} a The vector that needs projecting
			 * @param {Cartesian2} b The vector to project onto
			 * @param {Cartesian2} result The result cartesian
			 * @returns {Cartesian2} The modified result parameter
			 */
			static Cartesian2 projectVector(Cartesian2& a, Cartesian2& b) {
				double scalar = Cartesian2::dot(a, b) / Cartesian2::dot(b, b);
				return Cartesian2::multiplyByScalar(b, scalar);
			};

			/**
			 * Compares the provided Cartesians componentwise and returns
			 * <code>true</code> if they are equal, <code>false</code> otherwise.
			 *
			 * @param {Cartesian2} [left] The first Cartesian.
			 * @param {Cartesian2} [right] The second Cartesian.
			 * @returns {boolean} <code>true</code> if left and right are equal, <code>false</code> otherwise.
			 */
			static bool equals(Cartesian2& left, Cartesian2& right) {
				Cartesian2 result;
				return (
					left.x == right.x &&
					left.y == right.y
					);
			};

			/**
			 * @private
			 */
			static bool equalsArray(Cartesian2& cartesian, DoubleVector& array1, size_t offset) {
				return (
					cartesian.x == array1[offset] &&
					cartesian.y == array1[offset + 1]
					);
			};

			/**
			 * Compares the provided Cartesians componentwise and returns
			 * <code>true</code> if they pass an absolute or relative tolerance test,
			 * <code>false</code> otherwise.
			 *
			 * @param {Cartesian2} [left] The first Cartesian.
			 * @param {Cartesian2} [right] The second Cartesian.
			 * @param {number} [relativeEpsilon=0] The relative epsilon tolerance to use for equality testing.
			 * @param {number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
			 * @returns {boolean} <code>true</code> if left and right are within the provided epsilon, <code>false</code> otherwise.
			 */
			bool equalsEpsilon(
				Cartesian2& left,
				Cartesian2& right,
				double relativeEpsilon = 0.0,
				double absoluteEpsilon = 0.0
			) {
				return (
					CesiumMath::equalsEpsilon(
						left.x,
						right.x,
						relativeEpsilon,
						absoluteEpsilon
					) &&
					CesiumMath::equalsEpsilon(
						left.y,
						right.y,
						relativeEpsilon,
						absoluteEpsilon
					)
					);
			};

			/**
			 * Computes the cross (outer) product of two Cartesians.
			 *
			 * @param {Cartesian2} left The first Cartesian.
			 * @param {Cartesian2} right The second Cartesian.
			 * @param {Cartesian2} result The object onto which to store the result.
			 * @returns {Cartesian2} The cross product.
			 */
			static double cross(Cartesian2& left, Cartesian2& right) {
				return left.x * right.y - left.y * right.x;
			};

			/**
			 * Computes the midpoint between the right and left Cartesian.
			 * @param {Cartesian2} left The first Cartesian.
			 * @param {Cartesian2} right The second Cartesian.
			 * @param {Cartesian2} result The object onto which to store the result.
			 * @returns {Cartesian2} The midpoint.
			 */
			static Cartesian2 midpoint(Cartesian2& left, Cartesian2& right) {
				Cartesian2 result;

				result.x = (left.x + right.x) * 0.5;
				result.y = (left.y + right.y) * 0.5;

				return result;
			};

			/**
			 * An immutable Cartesian2 instance initialized to (0.0, 0.0, 0.0).
			 *
			 * @type {Cartesian2}
			 * @constant
			 */
			static const Cartesian2 ZERO;

			/**
			 * An immutable Cartesian2 instance initialized to (1.0, 1.0, 1.0).
			 *
			 * @type {Cartesian2}
			 * @constant
			 */
			static const Cartesian2 ONE;

			/**
			 * An immutable Cartesian2 instance initialized to (1.0, 0.0, 0.0).
			 *
			 * @type {Cartesian2}
			 * @constant
			 */
			static const Cartesian2 UNIT_X;

			/**
			 * An immutable Cartesian2 instance initialized to (0.0, 1.0, 0.0).
			 *
			 * @type {Cartesian2}
			 * @constant
			 */
			static const Cartesian2 UNIT_Y;

			/**
			 * Compares this Cartesian against the provided Cartesian componentwise and returns
			 * <code>true</code> if they are equal, <code>false</code> otherwise.
			 *
			 * @param {Cartesian2} [right] The right hand side Cartesian.
			 * @returns {boolean} <code>true</code> if they are equal, <code>false</code> otherwise.
			 */
			bool equals(Cartesian2& right) {
				return Cartesian2::equals(*this, right);
			};

			/**
			 * Compares this Cartesian against the provided Cartesian componentwise and returns
			 * <code>true</code> if they pass an absolute or relative tolerance test,
			 * <code>false</code> otherwise.
			 *
			 * @param {Cartesian2} [right] The right hand side Cartesian.
			 * @param {number} [relativeEpsilon=0] The relative epsilon tolerance to use for equality testing.
			 * @param {number} [absoluteEpsilon=relativeEpsilon] The absolute epsilon tolerance to use for equality testing.
			 * @returns {boolean} <code>true</code> if they are within the provided epsilon, <code>false</code> otherwise.
			 */
			bool equalsEpsilon(
				Cartesian2& right,
				double relativeEpsilon,
				double absoluteEpsilon
			) {
				return Cartesian2::equalsEpsilon(
					*this,
					right,
					relativeEpsilon,
					absoluteEpsilon
				);
			};

			/**
			 * Creates a string representing this Cartesian in the format '(x, y, z)'.
			 *
			 * @returns {string} A string representing this Cartesian in the format '(x, y, z)'.
			 */
			String toString() {
				return str_format("%f,%f", x, y);
			};
		};

	}
}

#endif

